/**
 * Fichier Journal.java : Définition de classe fr.unice.gnpt.core.util.Journal :
 * Journalisation
 * 
 * Auteurs : Groupe GNPT : Gastinel, Nicolas, Pellegrino, Trovato
 * 
 * $Id: Journal.java 460 2008-11-12 00:38:26Z Cyprien NICOLAS $
 */
package fr.unice.gnpt.core.util;

import java.io.IOException;
import java.util.Date;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.StreamHandler;

/**
 * Classe de journalisation des événements
 * <p>
 * Cette classe est utilisée pour relever diverses informations tout au long de
 * l'exécution de l'application, notamment en ce qui concerne les instantiations
 * de certaines classes primordiales, et les étapes cruciales du fonctionnement
 * de l'application.
 * <p>
 * Cette classe se base sur l'API {@code java.util.logging}.
 * 
 * @author gnpt
 * @version 0.1.$LastChangedRevision: 460 $
 */
public class Journal {

	/**
	 * L'instance de classe (Singleton Pattern)
	 */
	private static Journal instance = null;

	/**
	 * Le Logger utilisé par cette classe
	 */
	private Logger journal;

	/**
	 * Les Handlers utilisé par cette classe
	 */
	private FileHandler fileHndlr;
	private StreamHandler strmHndlr;

	/**
	 * Est-ce qu'on affiche le contenu du journal sur la console ?
	 */
	private static boolean printToSysout = false;

	/**
	 * Constructeur protégé de la classe Journal.
	 * 
	 * @param nomFichier
	 *            le nom du fichier où écrire le journal.
	 * @throws IOException
	 * @throws SecurityException
	 */
	protected Journal(String nomFichier) throws SecurityException, IOException {
		// Instantiation du logger
		this.journal = Logger.getLogger("fr.unice.gnpt");
		// Ecriture du journal dans un fichier texte
		this.fileHndlr = new FileHandler(nomFichier);
		this.fileHndlr.setFormatter(new SimpleFormatter());
		this.journal.addHandler(this.fileHndlr);
		// Ecriture du journal sur la sortie standard.
		if (Journal.printToSysout) {
			this.journal.addHandler(this.getStrmHndlr());
		} else {
			this.strmHndlr = null;
		}
		this.journal.setLevel(Level.ALL);
		this.journal.setUseParentHandlers(false);

		this.journal.info("Journalisation démarrée à "
				+ (new Date()).toString());
		this.journal.info("Le journal est écrit dans " + nomFichier);
	}

	/**
	 * Méthode de terminaison de la journalisation. Détruit l'objet Journal
	 * après avoir libéré les ressources associées.
	 */
	public void terminer() {
		this.journal.info("Journalisation terminée à "
				+ (new Date()).toString());
		this.fileHndlr.close();
		if (this.strmHndlr != null) {
			// On appelle flush plutôt que close pour ne pas fermer System.out
			this.strmHndlr.flush();
		}
		Journal.instance = null;
	}

	/**
	 * Accesseur pour l'objet Journal de l'application.
	 * 
	 * @param nomFichier
	 *            le nom du fichier où écrire le journal.
	 * @return l'objet Journal ainsi créé.
	 */
	public static Journal getJournal(String nomFichier) {
		if (Journal.instance == null) {
			try {
				Journal.instance = new Journal(nomFichier);
			} catch (SecurityException e) {
				System.err
						.println("SecurityException lors de la création du journal");
				e.printStackTrace(System.err);
			} catch (IOException e) {
				System.err
						.println("IOException lors de la création du journal");
				e.printStackTrace(System.err);
			}
		}
		return Journal.instance;
	}

	/**
	 * Accesseur pour l'objet Journal de l'application. Le journal sera écrit
	 * dans le fichier {@code "%t/fr.unice.gnpt.Main.log"} où {@code %t} est le
	 * répertoire temporaire du système d'exploitation.
	 * 
	 * @return l'objet Journal ainsi créé.
	 */
	public static Journal getJournal() {
		return Journal.getJournal("%t/fr.unice.gnpt.Main.log");
	}

	/**
	 * Accesseur statique pour l'affichage du journal sur {@code System.out}.
	 * 
	 * @return <code>true</code> si le journal est écrit sur la sortie standard.
	 */
	public static boolean isPrintedToSysout() {
		return Journal.printToSysout;
	}

	/**
	 * Mutateur statique pour l'affichage du journal sur {@code System.out}. La
	 * modification est prise en compte immédiatement si l'objet Journal a déjà
	 * été instancié à travers la méthode {@link Journal#getJournal()}.
	 * 
	 * @param printToSysout
	 *            <code>true</code> pour faire afficher le journal sur la sortie
	 *            standard.
	 */
	public static void setPrintToSysout(boolean printToSysout) {
		Journal.printToSysout = printToSysout;
		Journal self = Journal.instance;
		if (self != null) {
			if (printToSysout) {
				self.journal.addHandler(self.getStrmHndlr());
			} else {
				self.journal.removeHandler(self.getStrmHndlr());
			}
		}
	}

	/**
	 * Méthode protégée pour créer le handler spécifique à {@code System.out}.
	 * 
	 * @return le strmHndlr
	 */
	protected StreamHandler getStrmHndlr() {
		if (this.strmHndlr == null)
			this.strmHndlr = new StreamHandler(System.out,
					new SimpleFormatter());
		return this.strmHndlr;
	}

	/**
	 * Méthode protégée de journalisation. Elle inspecte la StackTrace pour
	 * trouver l'origine de la demande de journalisation.
	 * <p>
	 * Si la création du journal depuis la méthode {@link Journal#getJournal()},
	 * alors l'écriture se fera sur la sortie standard.
	 * 
	 * @param level
	 *            le niveau du message.
	 * @param message
	 *            le message à afficher.
	 */
	protected void log(Level level, String message) {
		StackTraceElement[] stea = Thread.currentThread().getStackTrace();
		// Elément 0 : l'appel à la méthode en cours d'exécution
		// Elément 1 : l'appel à la méthode logXXX() de this
		// Elément 2+: la méthode qui a appelé Journal (normalement)
		int i = 2, len = stea.length;
		while (i < len
				&& stea[i].getClassName().equals(
						this.getClass().getCanonicalName())) {
			i++;
		}
		if (i < len) {
			StackTraceElement e = stea[i];
			if (this.journal != null) {
				this.journal.logp(level, e.getClassName(), e.getMethodName()
						+ ":" + e.getLineNumber(), message);
			} else {
				System.out.println(level.toString() + " dans "
						+ e.getClassName() + "." + e.getMethodName() + ":"
						+ e.getLineNumber() + ":\n\t" + message);
			}
		}
	}

	/**
	 * Méthode de journalisation d'un message à caractère informatif.
	 * 
	 * @param message
	 *            le message d'information à journaliser.
	 */
	public void logInfo(String message) {
		this.log(Level.INFO, message);
	}

	/**
	 * Méthode de journalisation d'un message à caractère d'avertissement.
	 * 
	 * @param message
	 *            le message d'information à journaliser.
	 */
	public void logAvertissement(String message) {
		this.log(Level.WARNING, message);
		if (Journal.printToSysout) {
			this.strmHndlr.flush();
		}
	}

	/**
	 * Méthode de journalisation d'un message à caractère sévère.
	 * 
	 * @param message
	 *            le message d'information à journaliser.
	 */
	public void logErreur(String message) {
		this.log(Level.SEVERE, message);

		// On flush sysout en cas d'erreurs sévères.
		if (Journal.printToSysout) {
			this.strmHndlr.flush();
		}
	}

	/**
	 * Méthode de journalisation d'une exception.
	 * 
	 * @param exception
	 *            l'exception à journaliser.
	 * @param message
	 *            un message d'information à ajouter.
	 * @param stackTrace
	 *            une valeur booléenne indiquant si on journalise la pile
	 *            d'exécution à l'origine de l'exception.
	 * @param recursif
	 *            une valeur booléenne indiquant si on enregistre toute la
	 *            chaîne d'Exceptions. {@code false} par défaut.
	 */
	public void logException(Exception exception, String message,
			boolean stackTrace, boolean recursif) {
		if (message != null) {
			this.logErreur(message);
		}
		Throwable cause = exception;
		do {
			this.logErreur(" " + cause.toString());
		} while (recursif && (cause = cause.getCause()) != null);
		for (StackTraceElement e : exception.getStackTrace()) {
			this.logErreur(e.toString());
		}
	}

	/**
	 * Méthode de journalisation d'une exception.
	 * 
	 * @param exception
	 *            l'exception à journaliser.
	 * @param message
	 *            un message d'information à ajouter.
	 * @param stackTrace
	 *            une valeur booléenne indiquant si on journalise la pile
	 *            d'exécution à l'origine de l'exception.
	 */
	public void logException(Exception exception, String message,
			boolean stackTrace) {
		this.logException(exception, message, stackTrace, false);
	}

	/**
	 * Méthode de journalisation d'une exception.
	 * 
	 * @param exception
	 *            l'exception à journaliser.
	 * @param message
	 *            un message d'information à ajouter.
	 */
	public void logException(Exception exception, String message) {
		this.logException(exception, message, false, false);
	}

	/**
	 * Méthode de journalisation d'une exception.
	 * 
	 * @param exception
	 *            l'exception à journaliser.
	 * @param stackTrace
	 *            une valeur booléenne indiquant si on journalise la pile
	 *            d'exécution à l'origine de l'exception.
	 */
	public void logException(Exception exception, boolean stackTrace) {
		this.logException(exception, null, stackTrace, false);
	}

	/**
	 * Méthode de journalisation d'une exception.
	 * 
	 * @param exception
	 *            l'exception à journaliser.
	 */
	public void logException(Exception exception) {
		this.logException(exception, null, false, false);
	}
}
